#include "minicon.h"
#include "miniopt.h"

extern int err_ret(int errcode, const char *msg, const WCHAR *name);
extern bool real_filename(const WCHAR *nm, WCHAR *out, DWORD ms, bool norm, int must_exist);

#if defined(_WIN64) || defined(_DEBUG) || !defined(_M_IX86)
static DWORD __forceinline lldiv( __int64 &dividend, DWORD divisor )
{ DWORD reminder = (DWORD)(dividend % divisor); dividend /= divisor; return reminder; }
#else
DWORD __fastcall lldiv( __int64 &dividend, DWORD divisor );
#endif

#ifndef IO_REPARSE_TAG_SYMLINK
# define IO_REPARSE_TAG_SYMLINK       (0xA000000CL)
#endif

//-----------------------------------------------------------------------------

static void show_ft( const FILETIME & ft )
{
   FILETIME lft; SYSTEMTIME stm;
   ::FileTimeToLocalFileTime(&ft, &lft);
   ::FileTimeToSystemTime(&lft, &stm);
   __int64 i64 = ft.dwHighDateTime;
   i64 = (i64 << 32) | ft.dwLowDateTime;
   DWORD ns100 = lldiv(i64, 10*1000*1000);
   Con::f("%hu.%02hu.%02hu-%02hu:%02hu:%02hu," "%07u", // "%03u"
      stm.wYear,stm.wMonth,stm.wDay, stm.wHour,stm.wMinute,stm.wSecond, ns100//10000
   );
}

static struct attr_name { DWORD mask; const char *name; } attrs[] =
{ { FILE_ATTRIBUTE_DIRECTORY    , "dir "    }
, { FILE_ATTRIBUTE_READONLY     , "r/o "    }
, { FILE_ATTRIBUTE_ARCHIVE      , "arch "   }
, { FILE_ATTRIBUTE_HIDDEN       , "hid "    }
, { FILE_ATTRIBUTE_SYSTEM       , "sys "    }
, { FILE_ATTRIBUTE_COMPRESSED   , "compr "  }
, { FILE_ATTRIBUTE_OFFLINE      , "off "    }
, { FILE_ATTRIBUTE_TEMPORARY    , "temp "   }
, { FILE_ATTRIBUTE_REPARSE_POINT, "link "   }
, { FILE_ATTRIBUTE_SPARSE_FILE  , "Sparse " }
};

static int attr2s( DWORD attr, char *ps, bool long_names, DWORD rp )
{
   int nc = 0;
   for (int i = 0; i < idim(attrs); ++i) {
      if (0 != (attr & attrs[i].mask)) {
         const char *n = attrs[i].name;
         if (long_names) {
            ::lstrcpyA(ps+nc, n); nc += ::lstrlenA(n);
            if (*n == 'l') { // 'l'ink
               const char *ts = NULL;
               if  ( rp == IO_REPARSE_TAG_MOUNT_POINT ) ts = "/mpt ";
               else if ( rp == IO_REPARSE_TAG_SYMLINK ) ts = "/sym ";
               if ( ts ) {
                  ::lstrcpyA(ps+nc-1, ts); nc += ::lstrlenA(ts) - 1;
               }
            }
         }
         else ps[nc++] = *n;
      }
      else if (!long_names) ps[nc++] = '-';
   }
   if (long_names && nc > 0) --nc;
   ps[nc] = '\0';
   return nc;
}

//-----------------------------------------------------------------------------

static int pathname(WCHAR *base, WCHAR *fname, int opts)
{
   WCHAR *pw, fnm[MAX_PATH], abs_name[MAX_PATH], real_name[2*MAX_PATH];
   char ss[100];

   if (!base) pw = fname;
   else {
      fnm[MAX_PATH-1] = L'\0'; ::lstrcpynW(fnm, base, MAX_PATH-1);
      pw = fnm + ::lstrlen(fnm);
      while (pw > fnm && !Opt::is_slash(pw[-1])) --pw;
      ::lstrcpynW(pw, fname, MAX_PATH-1-(int)(size_t)(pw-fnm));
      pw = fnm;
   }

   WIN32_FIND_DATAW ff;
   HANDLE hf = ::FindFirstFileW(pw, &ff);
   if ( INVALID_HANDLE_VALUE != hf ) { ::FindClose(hf); }

   if (0 != (opts & (1 << ('v'-'a')))) // special case -- verbose mode
   {
      if (!::GetFullPathNameW(pw,MAX_PATH, abs_name, NULL))
         ::lstrcpyW(abs_name, L"failed");
      if (!real_filename(pw,real_name,MAX_PATH*2,true, 0))
         ::lstrcpyW(real_name, L"failed");

      Con::o(pw); Con::n();
      Con::o("  "); Con::o(abs_name);  Con::n();
      Con::o("  "); Con::o(real_name); Con::n();

      if (INVALID_HANDLE_VALUE != hf && 0 != (opts & (1 << ('a'-'a'))))
      { attr2s(ff.dwFileAttributes,ss,true, ff.dwReserved0); Con::f("  Attributes: %s\n",ss); }

      if (INVALID_HANDLE_VALUE != hf && 0 != (opts & (1 << ('t'-'a')))) {
         Con::o("  Time: u=");
         show_ft(ff.ftLastWriteTime);  Con::o(" c=");
         show_ft(ff.ftCreationTime);   Con::o(" a=");
         show_ft(ff.ftLastAccessTime); Con::n();
      }

      if (INVALID_HANDLE_VALUE != hf && 0 != (opts & (1 << ('z'-'a')))) {
         __int64 i64 = ff.nFileSizeHigh; i64 = (i64 << 32) | ff.nFileSizeLow;
         Con::o("  Size: "); Con::o(i64);
         __int64 c64 = i64;
         DWORD csz_hi = 0, csz_lo = ::GetCompressedFileSizeW(pw, &csz_hi);
         if ( csz_lo != INVALID_FILE_SIZE || ::GetLastError() == NO_ERROR ) {
            c64 = csz_hi; c64 = (c64 << 32) | csz_lo;
         }
         if ( i64 != c64 ) { Con::o(" / "); Con::o(c64); }
         Con::n();
      }
      return 0;
   }

   if (0 == (opts & (1 << ('r'-'a')))) {
      if (::GetFullPathNameW(pw,MAX_PATH, abs_name, NULL)) pw = abs_name;
      else return -51;
   } else {
      if (real_filename(pw,real_name,MAX_PATH*2,true,0)) pw = real_name;
      else return -52;
   }
   if (0 != (opts & (1 << ('s'-'a')))) {
      if (::GetShortPathNameW(pw, fnm, MAX_PATH)) pw = fnm;
   }

   bool first = true;
   if (INVALID_HANDLE_VALUE != hf && 0 != (opts & (1 << ('a'-'a')))) { //--attr
      first = false;
      Con::o(ss, attr2s(ff.dwFileAttributes, ss, false, 0));
   }
   if (INVALID_HANDLE_VALUE != hf && 0 != (opts & (1 << ('t'-'a')))) { //--time
      if (first) first = false; else Con::o(' ');
      FILETIME ft = ff.ftLastWriteTime;
      if (0 != (opts & (1 << ('l'-'a')))) ft = ff.ftLastAccessTime;
      if (0 != (opts & (1 << ('c'-'a')))) ft = ff.ftCreationTime;
      show_ft(ft);
   }
   if (INVALID_HANDLE_VALUE != hf && 0 != (opts & (1 << ('z'-'a')))) { //--size
      if (first) first = false; else Con::o(' ');
      __int64 i64 = ff.nFileSizeHigh; i64 = (i64 << 32) | ff.nFileSizeLow;
      Con::o(i64);
   }

   WCHAR *pb = pw, *drive = pw; int drive_len = 0;
   bool share = pw[0] == L'\\' && pw[1] == L'\\';
   if (share || pw[1] == L':') {
      pw += 2;
      if (share) {
         while (*pw && *pw != L'\\') ++pw; // skip server name
         if (*pw) ++pw;
         while (*pw && *pw != L'\\') ++pw; // skip share name
      }
      drive_len = (int)(size_t)(pw - drive);
   }

   WCHAR *path = pw, *pe = pb + ::lstrlen(pb) - 1; int path_len = 0;
   while (*pw && pe >= pw && *pe != L'\\') --pe;
   if (*pw && pe >= pw) {
      path_len = (int)(size_t)(pe+1 - pw); pw = pe + 1;
   }

   WCHAR *name = pw, *ext = pw; pe = pb + ::lstrlen(pb) - 1; int name_len = 0, ext_len = 0; 
   while (*pw && pe > pw && *pe != L'.') --pe;
   if (*pw) {
      if (pe <= pw) name_len = ::lstrlenW(pw);
      else {
         ext_len = ::lstrlen(ext = pe); name_len = (int)(size_t)(pe - pw);
      }
   }

   if (!first && (drive_len || path_len || name_len || ext_len)) Con::o(' ');
   if (0 != (opts & (1 << ('d'-'a')))) Con::o(drive, drive_len);
   if (0 != (opts & (1 << ('p'-'a')))) Con::o(path,  path_len );
   if (0 != (opts & (1 << ('n'-'a')))) Con::o(name,  name_len );
   if (0 != (opts & (1 << ('x'-'a')))) Con::o(ext,   ext_len  );
   Con::n();
   return 0;
}

//-----------------------------------------------------------------------------

int pathname(char mode, WCHAR **args,int na, int opts)
{
   int i, rc, wc; WCHAR *pw;

   if ( na <= 0 )
      return Opt::opt_err(Opt::Err_Missed_Param);
   
   for ( rc = i = 0; i < na && rc == 0; ++i )
   {
      pw = args[i];
      wc = (int)::lstrlenW(pw);
      while (wc > 2 && Opt::is_slash(pw[wc-1]) && Opt::is_slash(pw[wc-2])) pw[--wc] = L'\0';
      BOOL root = Opt::is_root(pw);
      while (!root && wc > 1 && Opt::is_slash(pw[wc-1])) pw[--wc] = L'\0';
      
      if ('p' != mode)
      {
         if (!root && !Opt::is_slash(pw[wc-1])) // "/" "C:\" as-is (for basename/dirname)
         {
            do { --wc; } while (wc >= 0 && !Opt::is_slash(pw[wc])); // remove trailing slash(es)
            if ('b' == mode) pw += wc + 1; // all after last slash
            else {
               if (wc < 0) { pw[0] = L'.'; wc = 1; } // empty dir_name -- "."
               else if (wc < 1) ++ wc; // dir_name("/") == "/"
               pw[wc] = L'\0'; // removes file part
            }
         }
         if ('b' == mode && na > 1) { // base-name with suffix
            int l1 = ::lstrlenW(pw);
            int l2 = ::lstrlenW(args[i+1]);
            if (l2 > 0 && l2 < l1 && 0 == Win::bcmp(args[i+1], pw+l1-l2, l2*2))
               pw[l1-l2] = L'\0';
         }
         Con::o(pw); Con::n();
         return 0;
      }

      if (!Opt::have_wildcard(pw)) rc = pathname(NULL, pw, opts);
      else {
         WIN32_FIND_DATAW ff;
         HANDLE hFind = ::FindFirstFileW(pw, &ff);
         BOOL res = INVALID_HANDLE_VALUE != hFind ? TRUE : FALSE;
         while ( res && rc == 0 ) {
            if (!Opt::is_dot(ff.cFileName)) 
               rc = pathname(pw, ff.cFileName, opts);
            res = ::FindNextFileW(hFind, &ff);
         }
         if (INVALID_HANDLE_VALUE != hFind) ::FindClose(hFind);
      }
   }

   return rc;
}

//=============================================================================
